/*
 * fui-worker worker-loader
 */

const webpack = require('webpack');
const loaderUtils = require('loader-utils');
const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
const { WorkerLoaderName, FileNamePrefix } = require('./constants');
const { resolve } = require('path');

// 正常 loader 处理逻辑
function loader() {
    const callback = this.async();
    this.cacheable(false);

    // 过滤掉当前的 worker-loader, 保留 worker 侧构建需要的其他 loader(babel-loader/ts-loader 等)
    const otherLoaders = this.loaders.filter((loader, index) => {
        if (index === this.loaderIndex) {
            return false;
        }
        
        return true;
    });
    /**
     * 拼接构建需要的 loader 字符串, 用于指定 childCompiler 的构建 loader
     * 比如: /path/to/babel-loader/lib/index.js!/path/to/ts-loader/index.js!
     */
    const loaderPath = otherLoaders.reduce((pre, loader) => `${pre}${loader.path}!`, '');

    /**
     * worker 独立构建的 entry
     * 构建 loader + worker 源码入口文件路径
     *
     * https://webpack.js.org/concepts/loaders/#inline
     * `!!` 实现在 childCompiler 中忽略其他所有 loader, 只保留主构建的 loader
     * 不然 worker 入口在 childCompiler 中会继续由 worker-loader 处理, 造成死循环
     */
    const workerEntry = `!!${loaderPath}${this.resourcePath}`;

    // 把资源纳入构建流程的依赖, 实现 dev 模式下的 watch
    this.addDependency(workerEntry);

    // 生成的 service 独立 bundle 名称
    const entryFileName = `${FileNamePrefix}index`;

    // 获取传递给 loader 的 options
    const options = Object.assign(loaderUtils.getOptions(this) || {}, this.options);

    // 创建 childCompiler, 用于实现 worker 构建为独立 js 资源
    const childCompiler = this._compilation.createChildCompiler(WorkerLoaderName, {
        globalObject: 'this',
    });
    childCompiler.context = this._compiler.context;

    // 指定独立构建的 entry 和生成 js 资源名称
    new SingleEntryPlugin(this.context, workerEntry, entryFileName).apply(childCompiler);

    // 设置 worker 侧的环境变量
    new webpack.DefinePlugin({
        __WORKER__: true,
    }).apply(childCompiler);

    // 添加 window 全局对象, 映射为 worker 线程全局对象 self
    // 如果在 worker 源码中添加, 可能没有前置到所有引用模块前
    new webpack.BannerPlugin({
        banner: 'self.window = self;',
        raw: true,
        entryOnly: true,
    }).apply(childCompiler);

    // 去除源码中的less css引用
    const regExp = /\.(css|less)$/;

    new webpack.NormalModuleReplacementPlugin(
        regExp,
        result => {
            if (regExp.test(result.request)) {
                result.request = resolve(__dirname, './empty.js');
            }

            if (regExp.test(result.resource)) {
                result.resource = resolve(__dirname, './empty.js');
            }
        },
    ).apply(childCompiler);

    const subCache = `subcache ${__dirname} ${workerEntry}`;
    childCompiler.hooks.compilation.tap(WorkerLoaderName, compilation => {
        if (compilation.cache) {
            if (!compilation.cache[subCache]) compilation.cache[subCache] = {};
            compilation.cache = compilation.cache[subCache];
        }
    });

    childCompiler.runAsChild((error, entries, compilation) => {
        if (!error && compilation.errors && compilation.errors.length) {
            // eslint-disable-next-line no-param-reassign
            error = compilation.errors[0];
        }

        // compatible with Array (v4) and Set (v5) prototypes
        const entry = entries && entries[0] && entries[0].files.values().next().value;
        if (!error && !entry) {
            // eslint-disable-next-line no-param-reassign
            error = Error(`${WorkerLoaderName}, no entry for ${workerEntry}`);
        }

        if (error) {
            return callback(error);
        }

        // 支持blob url形式
        return options.inline
            ? callback(
                null,
                // 插入代码的转译和压缩由主构建配置的 babel/ts loader 处理, 不需要 worker-worker 来处理
                // 添加 @ts-nocheck 避免 ts-check 报错
                // 修复export const 下 const不会被转译的问题
                `// @ts-nocheck
                export default window.URL.createObjectURL(new Blob([${JSON.stringify(compilation.assets[entry].source())}]));
                `
            )
            : callback(
                null,
                `// @ts-nocheck
                export default __webpack_public_path__ + ${JSON.stringify(entry)};
                `
            )
    });

    return;
}

module.exports = loader;
